/*
 * Copyright 2018 Rami Jemli
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http:// www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.ramijemli.percentagechartview.renderer;

import com.ramijemli.percentagechartview.AttrUtils;
import com.ramijemli.percentagechartview.IPercentageChartView;
import com.ramijemli.percentagechartview.Utils;
import com.ramijemli.percentagechartview.ValueAnimator;
import com.ramijemli.percentagechartview.callback.AdaptiveColorProvider;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.LinearShader;
import ohos.agp.render.Paint;
import ohos.agp.render.RadialShader;
import ohos.agp.render.Shader;
import ohos.agp.render.SweepShader;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;


/**
 * Ring mode renderer
 */
public class RingModeRenderer extends BaseModeRenderer implements OrientationBasedMode {
    /**
     * * The constant CAP_ROUND
     */
    public static final int CAP_ROUND = 0;
    /**
     * * The constant CAP_SQUARE
     */
    public static final int CAP_SQUARE = 1;
    /**
     * * The constant PercentageChartView_pcv_drawBackgroundBar
     */
    public static final String PERCENTAGECHARTVIEW_PCV_DRAWBACKGROUNDBAR = "pcv_drawBackgroundBar";
    /**
     * * The constant PercentageChartView_pcv_backgroundBarThickness
     */
    public static final String PERCENTAGECHARTVIEW_PCV_BACKGROUNDBARTHICKNESS = "pcv_backgroundBarThickness";
    /**
     * * The constant PercentageChartView_pcv_backgroundBarColor
     */
    public static final String PERCENTAGECHARTVIEW_PCV_BACKGROUNDBARCOLOR = "pcv_backgroundBarColor";
    /**
     * * The constant PercentageChartView_pcv_progressBarThickness
     */
    public static final String PERCENTAGECHARTVIEW_PCV_PROGRESSBARTHICKNESS = "pcv_progressBarThickness";
    /**
     * * The constant PercentageChartView_pcv_progressBarStyle
     */
    public static final String PERCENTAGECHARTVIEW_PCV_PROGRESSBARSTYLE = "pcv_progressBarStyle";
    /**
     * * The constant DEFAULT_BG_BAR_DP_WIDTH
     */
    private static final float DEFAULT_BG_BAR_DP_WIDTH = 16;
    /**
     * * The constant DEFAULT_PROGRESS_BAR_DP_WIDTH
     */
    private static final float DEFAULT_PROGRESS_BAR_DP_WIDTH = 16;
    /**
     * The constant M background bar paint
     */
    private Paint mBackgroundBarPaint;
    /**
     * The constant M draw background bar
     */
    private boolean mDrawBackgroundBar;
    /**
     * The constant M background bar thickness
     */
    private float mBackgroundBarThickness;
    /**
     * The constant M background bar color
     */
    private int mBackgroundBarColor;
    /**
     * The constant M provided bg bar color
     */
    private int mProvidedBgBarColor;
    /**
     * The constant M progress bar style
     */
    private Paint.StrokeCap mProgressBarStyle;
    /**
     * The constant M progress bar thickness
     */
    private float mProgressBarThickness;
    /**
     * The constant Tweak angle TO PUSH PROGRESS BAR OUT OF SWEEP GRADIENT'S WAY
     */
    private float tweakAngle;

    /**
     * Ring mode renderer
     *
     * @param view view
     */
    public RingModeRenderer(IPercentageChartView view) {
        super(view);
        init();
        setup();
    }

    /**
     * Ring mode renderer
     *
     * @param view  view
     * @param attrs attrs
     */
    public RingModeRenderer(IPercentageChartView view, AttrSet attrs) {
        super(view, attrs);
        init(attrs);
        setup();
    }

    /**
     * Init *
     *
     * @param attrs attrs
     */
    private void init(AttrSet attrs) {
        // BACKGROUND BAR DRAW STATE
        mDrawBackgroundBar = AttrUtils.getBooleanFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_DRAWBACKGROUNDBAR, true);

        // BACKGROUND WIDTH
        mBackgroundBarThickness = AttrUtils.getDimensionFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_BACKGROUNDBARTHICKNESS,
                Utils.dp2px(mView.getViewContext(), (int) DEFAULT_BG_BAR_DP_WIDTH));

        // BACKGROUND BAR COLOR
        mBackgroundBarColor = AttrUtils.getColorFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_BACKGROUNDBARCOLOR,
                Color.BLACK.getValue());

        // PROGRESS WIDTH
        mProgressBarThickness = AttrUtils.getDimensionFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_PROGRESSBARTHICKNESS,
                Utils.dp2px(mView.getViewContext(), (int) DEFAULT_PROGRESS_BAR_DP_WIDTH));

        // PROGRESS BAR STROKE STYLE
        int cap = AttrUtils.getIntFromAttr(attrs, PERCENTAGECHARTVIEW_PCV_PROGRESSBARSTYLE, CAP_ROUND);
        mProgressBarStyle = (cap == CAP_ROUND) ? Paint.StrokeCap.ROUND_CAP : Paint.StrokeCap.BUTT_CAP;
    }

    /**
     * Init
     */
    private void init() {
        // DRAW BACKGROUND BAR
        mDrawBackgroundBar = true;

        // BACKGROUND WIDTH
        mBackgroundBarThickness = Utils.dp2px(mView.getViewContext(), (int) DEFAULT_BG_BAR_DP_WIDTH);

        // BACKGROUND BAR COLOR
        mBackgroundBarColor = Color.BLACK.getValue();

        // PROGRESS BAR WIDTH
        mProgressBarThickness = Utils.dp2px(mView.getViewContext(), (int) DEFAULT_PROGRESS_BAR_DP_WIDTH);

        // PROGRESS BAR STROKE STYLE
        mProgressBarStyle = Paint.StrokeCap.ROUND_CAP;
    }

    /**
     * Setup
     */
    @Override
    void setup() {
        super.setup();
        mProvidedBgBarColor = -1;
        tweakAngle = 0;
        updateDrawingAngles();

        // BACKGROUND BAR
        mBackgroundBarPaint = new Paint();
        mBackgroundBarPaint.setAntiAlias(true);
        mBackgroundBarPaint.setStyle(Paint.Style.STROKE_STYLE);
        mBackgroundBarPaint.setColor(new Color(mBackgroundBarColor));
        mBackgroundBarPaint.setStrokeWidth(mBackgroundBarThickness);
        mBackgroundBarPaint.setStrokeCap(mProgressBarStyle);

        // PROGRESS PAINT
        mProgressPaint.setStyle(Paint.Style.STROKE_STYLE);
        mProgressPaint.setStrokeWidth(mProgressBarThickness);
        mProgressPaint.setStrokeCap(mProgressBarStyle);
    }

    /**
     * Measure *
     *
     * @param width         width
     * @param height        height
     * @param paddingLeft   padding left
     * @param paddingTop    padding top
     * @param paddingRight  padding right
     * @param paddingBottom padding bottom
     */
    @Override
    public void measure(int width, int height, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom) {
        int diameter = Math.min(width, height);
        float maxOffset = Math.max(mProgressBarThickness, mBackgroundBarThickness);

        int centerX = width / 2;
        int centerY = height / 2;
        float radius = (diameter - maxOffset) / 2;

        mCircleBounds = new RectFloat(centerX - radius,
                centerY - radius,
                centerX + radius,
                centerY + radius);

        float backgroundRadius = radius - (mBackgroundBarThickness / 2) + 1;

        mBackgroundBounds = new RectFloat(centerX - backgroundRadius,
                centerY - backgroundRadius,
                centerX + backgroundRadius,
                centerY + backgroundRadius);

        setupGradientColors(mCircleBounds);
        updateText();
    }

    /**
     * Draw *
     *
     * @param canvas canvas
     */
    @Override
    public void draw(Canvas canvas) {
        // BACKGROUND
        if (mDrawBackground) {
            canvas.drawArc(mBackgroundBounds, new Arc(0, 360, false), mBackgroundPaint);
        }

        // BACKGROUND BAR
        if (mDrawBackgroundBar) {
            if (mBackgroundBarThickness <= mProgressBarThickness) {
                canvas.drawArc(mCircleBounds, new Arc(mStartAngle + tweakAngle, -(360 - mSweepAngle + tweakAngle), false), mBackgroundBarPaint);
            } else {
                canvas.drawArc(mCircleBounds, new Arc(0, 360, false), mBackgroundBarPaint);
            }
        }

        // FOREGROUND
        if (mProgress != 0) {
            canvas.drawArc(mCircleBounds, new Arc(mStartAngle + tweakAngle, mSweepAngle, false), mProgressPaint);
        }

        // TEXT
        drawText(canvas);
    }

    /**
     * Destroy
     */
    @Override
    public void destroy() {
        super.destroy();
        if (mBgBarColorAnimator != null) {
            if (mBgBarColorAnimator.isRunning()) {
                mBgBarColorAnimator.cancel();
            }
            mBgBarColorAnimator.cancel();
        }
        mBgBarColorAnimator = null;
        mBackgroundBarPaint = null;
    }

    /**
     * Set adaptive color provider *
     *
     * @param adaptiveColorProvider adaptive color provider
     */
    @Override
    public void setAdaptiveColorProvider(AdaptiveColorProvider adaptiveColorProvider) {
        if (adaptiveColorProvider == null) {
            mProgressColorAnimator = mBackgroundColorAnimator = mTextColorAnimator = mBgBarColorAnimator = null;
            this.mAdaptiveColorProvider = null;
            mTextPaint.setColor(new Color(mTextColor));
            mBackgroundBarPaint.setColor(new Color(mBackgroundBarColor));
            mBackgroundPaint.setColor(new Color(mBackgroundColor));
            mProgressPaint.setColor(new Color(mProgressColor));
            mView.postInvalidate();
            return;
        }

        this.mAdaptiveColorProvider = adaptiveColorProvider;

        setupColorAnimations();
        updateProvidedColors(mProgress);
        mView.postInvalidate();
    }

    /**
     * Setup gradient colors *
     *
     * @param bounds bounds
     */
    @Override
    void setupGradientColors(RectFloat bounds) {
        if (mGradientType == -1) {
            return;
        }

        double ab = Math.pow(bounds.bottom - bounds.getVerticalCenter(), 2);
        tweakAngle = (float) Math.toDegrees(Math.acos((2 * ab - Math.pow(mProgressBarThickness / 2, 2)) / (2 * ab)));

        switch (mGradientType) {
            default:
            case GRADIENT_LINEAR:
                Point[] points = new Point[]{new Point(bounds.getHorizontalCenter(), bounds.top),
                        new Point(bounds.getVerticalCenter(), bounds.bottom)};
                mGradientShader = new LinearShader(points, mGradientDistributions, mGradientColors,
                        Shader.TileMode.CLAMP_TILEMODE);
                updateGradientAngle(mStartAngle);
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.LINEAR_SHADER);
                break;

            case GRADIENT_RADIAL:
                mGradientShader = new RadialShader(new Point(bounds.getHorizontalCenter(), bounds.getVerticalCenter()),
                        bounds.bottom - bounds.getVerticalCenter(),
                        mGradientDistributions, mGradientColors, Shader.TileMode.MIRROR_TILEMODE);
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.RADIAL_SHADER);
                break;

            case GRADIENT_SWEEP:
                mGradientShader = new SweepShader(bounds.getHorizontalCenter(), bounds.getVerticalCenter(),
                        mGradientColors, mGradientDistributions);

                if (!mView.isInEditMode()) {
                    // THIS BREAKS SWEEP GRADIENT'S PREVIEW MODE
                    updateGradientAngle(mStartAngle);
                }
                mProgressPaint.setShader(mGradientShader, Paint.ShaderType.SWEEP_SHADER);
                break;
        }
    }

    /**
     * Setup color animations
     */
    @Override
    void setupColorAnimations() {
        super.setupColorAnimations();
        if (mBgBarColorAnimator == null) {
            mBgBarColorAnimator = ValueAnimator.ofFloat(mBackgroundBarColor, mProvidedBgBarColor);
            mBgBarColorAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float value) {
                    mProvidedBgBarColor = (int) value;
                    mBackgroundBarPaint.setColor(new Color(mProvidedBgBarColor));
                }
            });

            mBgBarColorAnimator.setDuration(mAnimDuration);
        }
    }

    /**
     * Cancel animations
     */
    @Override
    void cancelAnimations() {
        super.cancelAnimations();
        if (mBgBarColorAnimator != null && mBgBarColorAnimator.isRunning()) {
            mBgBarColorAnimator.cancel();
        }
    }

    /**
     * Update animations *
     *
     * @param progress progress
     */
    @Override
    void updateAnimations(float progress) {
        super.updateAnimations(progress);

        if (mAdaptiveColorProvider == null) {
            return;
        }

        int providedBgBarColor = mAdaptiveColorProvider.provideBackgroundBarColor(progress);
        if (providedBgBarColor != -1 && providedBgBarColor != mProvidedBgBarColor) {
            int startColor = mProvidedBgBarColor != -1 ? mProvidedBgBarColor : mBackgroundBarColor;
            mBgBarColorAnimator.setFloatValues(startColor, providedBgBarColor);
            mBgBarColorAnimator.start();
        }
    }

    /**
     * Update provided colors *
     *
     * @param progress progress
     */
    @Override
    void updateProvidedColors(float progress) {
        super.updateProvidedColors(progress);
        if (mAdaptiveColorProvider == null) {
            return;
        }
        int providedBgBarColor = mAdaptiveColorProvider.provideBackgroundBarColor(progress);
        if (providedBgBarColor != -1 && providedBgBarColor != mProvidedBgBarColor) {
            mProvidedBgBarColor = providedBgBarColor;
            mBackgroundBarPaint.setColor(new Color(mProvidedBgBarColor));
        }
    }

    /**
     * Update drawing angles
     */
    @Override
    void updateDrawingAngles() {
        switch (orientation) {
            case ORIENTATION_COUNTERCLOCKWISE:
                mSweepAngle = -(mProgress / DEFAULT_MAX * 360);
                break;

            default:
            case ORIENTATION_CLOCKWISE:
                mSweepAngle = mProgress / DEFAULT_MAX * 360;
                break;
        }
    }

    /**
     * Update gradient angle *
     *
     * @param angle angle
     */
    @Override
    void updateGradientAngle(float angle) {
        if (mGradientType == -1 || mGradientType == GRADIENT_RADIAL) {
            return;
        }
        Matrix matrix = new Matrix();
        matrix.postRotate(angle, mCircleBounds.getHorizontalCenter(), mCircleBounds.getVerticalCenter());
        mGradientShader.setShaderMatrix(matrix);
    }

    /**
     * Get orientation int
     *
     * @return the int
     */
    public int getOrientation() {
        return orientation;
    }

    /**
     * Set orientation *
     *
     * @param orientation orientation
     */
    public void setOrientation(int orientation) {
        if (this.orientation == orientation) {
            return;
        }
        this.orientation = orientation;
        updateDrawingAngles();
    }

    /**
     * Set start angle *
     *
     * @param startAngle start angle
     */
    @Override
    public void setStartAngle(float startAngle) {
        if (this.mStartAngle == startAngle) {
            return;
        }
        this.mStartAngle = startAngle;
        if (mGradientType == GRADIENT_SWEEP) {
            updateGradientAngle(startAngle);
        }
    }

    /**
     * Is draw background bar enabled boolean
     *
     * @return the boolean
     */
    public boolean isDrawBackgroundBarEnabled() {
        return mDrawBackgroundBar;
    }

    /**
     * Set draw background bar enabled *
     *
     * @param drawBackgroundBar draw background bar
     */
    public void setDrawBackgroundBarEnabled(boolean drawBackgroundBar) {
        if (mDrawBackgroundBar == drawBackgroundBar) {
            return;
        }
        this.mDrawBackgroundBar = drawBackgroundBar;
    }

    /**
     * Get background bar color int
     *
     * @return the int
     */
    public int getBackgroundBarColor() {
        if (!mDrawBackgroundBar) {
            return -1;
        }
        return mBackgroundBarColor;
    }

    /**
     * Set background bar color *
     *
     * @param backgroundBarColor background bar color
     */
    public void setBackgroundBarColor(int backgroundBarColor) {
        if (!mDrawBackgroundBar || (mAdaptiveColorProvider != null && mAdaptiveColorProvider.provideBackgroundBarColor(mProgress) != -1) || this.mBackgroundBarColor == backgroundBarColor) {
            return;
        }
        this.mBackgroundBarColor = backgroundBarColor;
        mBackgroundBarPaint.setColor(new Color(mBackgroundBarColor));
    }

    /**
     * Get background bar thickness float
     *
     * @return the float
     */
    public float getBackgroundBarThickness() {
        return mBackgroundBarThickness;
    }

    /**
     * Set background bar thickness *
     *
     * @param backgroundBarThickness background bar thickness
     */
    public void setBackgroundBarThickness(float backgroundBarThickness) {
        if (this.mBackgroundBarThickness == backgroundBarThickness) {
            return;
        }
        this.mBackgroundBarThickness = backgroundBarThickness;
        mBackgroundBarPaint.setStrokeWidth(backgroundBarThickness);
        measure(mView.getWidth(), mView.getHeight(), 0, 0, 0, 0);
    }

    /**
     * Get progress bar thickness float
     *
     * @return the float
     */
    public float getProgressBarThickness() {
        return mProgressBarThickness;
    }

    /**
     * Set progress bar thickness *
     *
     * @param progressBarThickness progress bar thickness
     */
    public void setProgressBarThickness(float progressBarThickness) {
        if (this.mProgressBarThickness == progressBarThickness) {
            return;
        }
        this.mProgressBarThickness = progressBarThickness;
        mProgressPaint.setStrokeWidth(progressBarThickness);
        measure(mView.getWidth(), mView.getHeight(), 0, 0, 0, 0);
    }

    /**
     * Get progress bar style int
     *
     * @return the int
     */
    public int getProgressBarStyle() {
        return (mProgressBarStyle == Paint.StrokeCap.ROUND_CAP) ? CAP_ROUND : CAP_SQUARE;
    }

    /**
     * Set progress bar style *
     *
     * @param progressBarStyle progress bar style
     */
    public void setProgressBarStyle(int progressBarStyle) {
        if (progressBarStyle < 0 || progressBarStyle > 1) {
            throw new IllegalArgumentException("Text style must be a valid TextStyle constant.");
        }
        mProgressBarStyle = (progressBarStyle == CAP_ROUND) ? Paint.StrokeCap.ROUND_CAP : Paint.StrokeCap.BUTT_CAP;
        mProgressPaint.setStrokeCap(mProgressBarStyle);
    }

}
